{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Quantum Process Tomography with Q# and Python"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**NB**: This notebook requires additional packages to provide process tomography support. Dependencies for this sample can be installed as a new virtual environment by using `conda`:\n",
    "\n",
    "```sh\n",
    "conda env create -f environment.yml\n",
    "conda activate process-tomography\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Abstract ##"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this sample, we will demonstrate using Q# and Python together with the QInfer and QuTiP libraries for Python to characterize and verify quantum processes implemented in Q#.\n",
    "In particular, this sample will use *quantum process tomography* to learn about the behavior of a \"noisy\" Hadamard operation from the results of random Pauli measurements.\n",
    "\n",
    "So that we have some noise to learn, we will use the open systems simulator provided with the Quantum Development Kit to simulate the action of a noisy `H` operation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preamble ##"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can enable Q# support in Python by importing the `qsharp` package."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Preparing Q# environment...\n"
     ]
    }
   ],
   "source": [
    "import qsharp"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We'll also be using numerics in this sample, so we import NumPy as well."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we import plotting support and the QuTiP library, since these will be helpful to us in manipulating the quantum objects returned by the quantum process tomography functionality that we call later."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import qutip as qt\n",
    "qt.settings.colorblind_safe = True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, to run process tomography, we'll be using the [QInfer](http://qinfer.org) library, so we go on and import it here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "ERROR:duecredit:Failed to import duecredit due to No module named 'duecredit'\n",
      "C:\\Users\\cgran\\Anaconda3\\envs\\iqs-dev\\lib\\site-packages\\IPython\\parallel.py:12: ShimWarning: The `IPython.parallel` package has been deprecated since IPython 4.0. You should import from ipyparallel instead.\n",
      "  warn(\"The `IPython.parallel` package has been deprecated since IPython 4.0. \"\n",
      "C:\\Users\\cgran\\Anaconda3\\envs\\iqs-dev\\lib\\site-packages\\qinfer-1.0-py3.8.egg\\qinfer\\parallel.py:61: UserWarning: Could not import IPython parallel. Parallelization support will be disabled.\n"
     ]
    }
   ],
   "source": [
    "import qinfer as qi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tomography"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We start by defining a noise model to use in simulating the `H` operation. For example, suppose that nothing happens 10% of the time we call `H`, but that the other 90% of the time, `H` works perfectly. We can use QuTiP to define a channel representing this case by using `qt.to_super` to turn unitary operators into channels, then adding the channels for each of the two possibilities."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/latex": [
       "Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True\\begin{equation*}\\left(\\begin{array}{*{11}c}0.550 & 0.450 & 0.450 & 0.450\\\\0.450 & -0.350 & 0.450 & -0.450\\\\0.450 & 0.450 & -0.350 & -0.450\\\\0.450 & -0.450 & -0.450 & 0.550\\\\\\end{array}\\right)\\end{equation*}"
      ],
      "text/plain": [
       "Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True\n",
       "Qobj data =\n",
       "[[ 0.55  0.45  0.45  0.45]\n",
       " [ 0.45 -0.35  0.45 -0.45]\n",
       " [ 0.45  0.45 -0.35 -0.45]\n",
       " [ 0.45 -0.45 -0.45  0.55]]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "noisy_h = 0.9 * qt.to_super(qt.qip.operations.hadamard_transform()) + 0.1 * qt.to_super(qt.qeye(2))\n",
    "noisy_h"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once we have a channel describing the noise in our `H` operation, we can set that to the noise model used by the open systems simulator."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "noise_model = qsharp.get_noise_model_by_name('ideal')\n",
    "noise_model.h = noisy_h\n",
    "qsharp.set_noise_model(noise_model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can then define a new Q# operation that takes a preparation and a measurement, then returns the result of performing that tomographic measurement on the noisy Hadamard operation that we defined the noise model for immediately above."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%qsharp\n",
    "open Microsoft.Quantum.Characterization;\n",
    "\n",
    "operation DrawTomographyMeasurement(prep : Pauli, meas : Pauli) : Result {\n",
    "    return SingleQubitProcessTomographyMeasurement(prep, meas, H);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In quantum tomography experiments, it is often helpful to select preparation and measurement bases at random [arXiv:1302.0932](https://arxiv.org/abs/1302.0932) so as to average out random drift in noise processes, so we'll next write an operation that does this random sampling within a Q# program directly, allowing us to batch up measurements with less round trips between our host and our quantum program."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%qsharp\n",
    "open Microsoft.Quantum.Random;\n",
    "open Microsoft.Quantum.Arrays;\n",
    "\n",
    "operation DrawTomographyMeasurementInRandomBasis() : (Pauli, Pauli, Result) {\n",
    "    let prep = DrawRandomPauli();\n",
    "    let meas = DrawRandomPauli();\n",
    "    return (prep, meas, DrawTomographyMeasurement(prep, meas));\n",
    "}\n",
    "\n",
    "operation DrawTomographyBatch(nMeasurements : Int) : (Pauli, Pauli, Result)[] {\n",
    "    return DrawMany(DrawTomographyMeasurementInRandomBasis, nMeasurements, ());\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(0, 2, 0), (1, 3, 1), (0, 0, 0), (2, 3, 1), (1, 1, 0)]"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "DrawTomographyBatch.simulate(nMeasurements=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, we define a function for quantum tomography itself."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def projector(P):\n",
    "    return (qt.qeye(2) + P) / 2.0\n",
    "\n",
    "def single_qubit_process_tomography(\n",
    "    n_measurements_per_batch=100,\n",
    "    n_batches=40,\n",
    "    n_particles=128_000\n",
    "):\n",
    "\n",
    "    # We start by preparing a model for quantum tomography.\n",
    "    # This model predicts the output of different measurements,\n",
    "    # conditioned on different hypotheses.\n",
    "    print(\"Preparing tomography model...\")\n",
    "    state_basis = qi.tomography.pauli_basis(1)\n",
    "    prior = qi.tomography.BCSZChoiDistribution(state_basis)\n",
    "    model = qi.tomography.TomographyModel(prior.basis)\n",
    "\n",
    "    # Once we have a model, we can define how our knowledge\n",
    "    # about that model is updated with new data.\n",
    "    updater = qi.SMCUpdater(model, n_particles, prior)\n",
    "\n",
    "    # To learn the channel, we loop through each different\n",
    "    # measurement and use the updater to incorporate those results.\n",
    "    print(\"Performing tomography...\")\n",
    "    for idx_batch in range(n_batches):\n",
    "        print(f\"\\tBatch {idx_batch + 1} of {n_batches}...\")\n",
    "        # Get a batch of measurements from our tomography operation\n",
    "        # above.\n",
    "        print(\"\\t\\tRunning measurements...\")\n",
    "        batch = DrawTomographyBatch.simulate_noise(nMeasurements=n_measurements_per_batch)\n",
    "        \n",
    "        # Process each of the results and update our knowledge using\n",
    "        # Bayesian inference.\n",
    "        print(\"\\t\\tUpdating posterior distribution...\")\n",
    "        for prep, meas, result in batch:\n",
    "            # Convert into a QuTiP object by using the standard transformation\n",
    "            # between state and process tomography.\n",
    "            qobj = 2.0 * qt.tensor(\n",
    "                projector(\n",
    "                    qsharp.Pauli(prep).as_qobj()\n",
    "                ).trans(),\n",
    "                projector(\n",
    "                    qsharp.Pauli(meas).as_qobj()\n",
    "                )\n",
    "            )\n",
    "            expparams = np.array(\n",
    "                [(model.basis.state_to_modelparams(qobj),)],\n",
    "                dtype=model.expparams_dtype\n",
    "            )\n",
    "            updater.update(1 - result, expparams)\n",
    "        \n",
    "    return (\n",
    "        # After we've collected all of our data, we can use the\n",
    "        # mean (that is, the average) from our updater to estimate\n",
    "        # the channel.\n",
    "        # NB: We multiply by 2 to turn into a Choi–Jamiłkowski operator instead\n",
    "        #     of a Choi–Jamiłkowski state.\n",
    "        2.0 * model.basis.modelparams_to_state(updater.est_mean()),\n",
    "        # We also return the full distribution itself so that we\n",
    "        # can look at other properties such as error bars.\n",
    "        updater\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here, we ask for 10,000 measurements from the noisy Hadamard operation that we defined above.\n",
    "\n",
    "> **NOTE**: The next cell may take several minutes to run."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We then plot the estimated and actual channels as Hinton diagrams, showing how each acts on the Pauli operators $X$, $Y$ and $Z$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Preparing tomography model...\n",
      "Performing tomography...\n",
      "\tBatch 1 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\cgran\\Anaconda3\\envs\\iqs-dev\\lib\\site-packages\\qinfer-1.0-py3.8.egg\\qinfer\\distributions.py:397: ApproximationWarning: Numerical error in covariance estimation causing positive semidefinite violation.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\tBatch 2 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 3 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 4 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 5 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 6 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 7 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 8 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 9 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 10 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 11 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 12 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 13 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 14 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 15 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 16 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 17 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 18 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 19 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 20 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 21 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 22 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 23 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 24 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 25 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 26 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 27 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 28 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 29 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 30 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 31 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 32 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 33 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 34 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 35 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 36 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 37 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 38 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 39 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n",
      "\tBatch 40 of 40...\n",
      "\t\tRunning measurements...\n",
      "\t\tUpdating posterior distribution...\n"
     ]
    }
   ],
   "source": [
    "est_channel, updater = single_qubit_process_tomography()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(None, <AxesSubplot:xlabel='Actual'>)"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 864x288 with 4 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, (left, right) = plt.subplots(ncols=2, figsize=(12, 4))\n",
    "plt.sca(left)\n",
    "plt.xlabel('Estimated', fontsize='x-large')\n",
    "qt.visualization.hinton(est_channel, ax=left)\n",
    "plt.sca(right)\n",
    "plt.xlabel('Actual', fontsize='x-large')\n",
    "qt.visualization.hinton(noisy_h, ax=right)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We also obtain a wealth of other information as well, such as the covariance matrix over each parameter of the resulting channel.\n",
    "This shows us which parameters we are least certain about, as well as how those parameters are correlated with each other."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "plt.figure(figsize=(10, 10))\n",
    "updater.plot_covariance()\n",
    "plt.xticks(rotation=90)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "## Epilogue"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for component, version in sorted(qsharp.component_versions().items(), key=lambda x: x[0]):\n",
    "    print(f\"{component:20}{version}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "print(sys.version)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
